//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-07-19     ZhangYihua   first version
//
// Description  : Single Error-bit Correction and Double Error-bit Detecttion.
//                Generate error pattern according to DW and CW.
//################################################################################

module sec_ded_gep #(
parameter           DW                      = 32,       // original data width, 1<=DW<=502

// the following parameters are calculated automatically
parameter           CW                      = ecc_cw_f(DW,"SEC_DED")  // the smallest redundant check width but meet DW <= 2^(CW-1) - CW
) ( 
output              [DW*CW-1:0]             ep_ary,     // pattern for each single error
output  reg         [DW*CW-1:0]             gm_ary      // generative matrix
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam [63:0]   EP_IDX_RANGE            = ep_idx_range_f(CW, DW);
localparam [31:0]   EP_IDX_LOW              = EP_IDX_RANGE[31:0];
localparam [31:0]   EP_IDX_HIGH             = EP_IDX_RANGE[63:32];

//################################################################################
// main
//################################################################################

// Single Error-bit Correction and Double Error-bit Detecttion:
// 1) there are CW+DW single errors in total, each single error maps a error pattern.
// 2) each error pattern consists of CW bits, so must pick out CW+DW bit-combinations from 1 to (2^CW)-1, and it meets the follows
// 3) the weight of each error pattern must be odd, for example 1, 3, 5, 7, .....
// 4) error patterns whose weight is 1 map to single check bit errors
// 5) total weight of all error patterns is as smalle as possible
// 6) weight distribution at each bit position is as even as possible
assign ep_ary = gen_ep_f(CW, DW);

always@(*) begin:TRANS
    integer i, j;
    for (i=0; i<CW; i=i+1) begin
        for (j=0; j<DW; j=j+1) begin
             gm_ary[i*DW+j] = ep_ary[j*CW+i];
        end
    end
end

`include "func_ecc.v"   // refer to ecc_cw_f()

// number of setting w bits to 1 from r bits position
function integer cmb_num_f;
    input integer r;    // redundant check width
    input integer w;    // weight of a binary data

    integer x;
    begin
        cmb_num_f = 1;
        x = r;
        repeat(w) begin
            cmb_num_f = cmb_num_f*x;
            x = x-1;
        end

        x = 1;
        repeat(w) begin
            cmb_num_f = cmb_num_f/x;
            x = x+1;
        end
    end
endfunction

function [63:0] ep_idx_range_f;
    input integer r;    // redundant check width
    input integer k;    // original data width

    integer wght, acc;
    reg [31:0] low, high;
    begin
        wght = 3;   // start weight
        acc  = 0;
        low  = 32'd0;
        while(acc<k) begin
            acc = acc+cmb_num_f(r, wght);

            if (acc<=k) begin
                low = acc;
            end

            wght = wght + 2;
        end
        high = acc;
        ep_idx_range_f = {high, low};
    end
endfunction

function integer weight_f;
    input [CW-1:0] bin;

    integer i;
    begin
        weight_f = 0;
        for (i=0; i<CW; i=i+1) begin
            if (bin[i]==1'b1) begin
                weight_f = weight_f+1;
            end
        end
    end
endfunction

function [DW*CW-1:0] gen_ep_f;
    input integer r;    // redundant check width
    input integer k;    // original data width

    integer wght, idx, i;
    reg [CW-1:0] ep_lib[EP_IDX_HIGH-1:0];
    reg [CW-1:0] bin, mask, mask_and, mask_or, back;
    reg [EP_IDX_HIGH-1:0] free_bmp;
    reg unchg;
    begin
        bin  = $unsigned(7);    // smallest number whose weight is 3
        wght = 3;
        idx  = 0;
        while(idx<EP_IDX_HIGH) begin
            if (weight_f(bin)==wght) begin
                ep_lib[idx] = bin;
                idx = idx+1;
            end

            if (bin=={CW{1'b1}}) begin
                bin = $unsigned(7);
                wght = wght+2;
            end else
                bin = bin+1'd1;
        end

        for (i=0; i<EP_IDX_LOW; i=i+1) begin
             gen_ep_f[i*CW+:CW] = ep_lib[i];
        end

        i   = EP_IDX_LOW;
        idx = EP_IDX_LOW;
        mask = {CW{1'b0}};
        back = {CW{1'b0}};
        unchg = 1'b1;
        free_bmp = {EP_IDX_HIGH{1'b1}};
        while(i<k) begin
            mask_and = mask & ep_lib[idx];
            mask_or  = mask | ep_lib[idx];

            if (((mask_and=={CW{1'b0}}) || (mask_or=={CW{1'b1}})) && free_bmp[idx]==1'b1) begin
                gen_ep_f[i*CW+:CW] = ep_lib[idx];
                free_bmp[idx] = 1'b0;

                if (mask_and!={CW{1'b0}})
                    mask = mask_and;
                else if (mask_or!={CW{1'b1}})
                    mask = mask_or;
                else
                    mask = {CW{1'b0}};

                if (back!={CW{1'b0}}) begin // restore mask as possible
                    bin  = (~mask) & back;
                    mask = mask |   bin;
                    back = back & (~bin);
                end

                unchg = 1'b0;   // ep_lib changed
                i = i+1;
            end

            if (idx==(EP_IDX_HIGH-1)) begin
                if (unchg==1'b1) begin      // can not find pefect pattern, so clear a bit of mask
                    bin = mask & (~back);
                    if (bin=={CW{1'b0}}) begin      // avoid dead loop
                        bin = mask;
                    end
                    bin = ((~bin) + 1'd1) & bin;    // find the first non-zero bit of bin
                    mask = mask & (~bin);
                    back = back |   bin;
                    if (back=={CW{1'b1}}) begin
                        back = {CW{1'b0}};
                    end
                end else begin
                    unchg = 1'b1;
                end

                idx = EP_IDX_LOW;   // restart a new search
            end else
                idx = idx+1;
        end
    end
endfunction

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
